package com.myown.algorithm;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * 随机算法 <p>
 *
 * @author lincky
 * @version v1.0.0
 * @time 2016/12/21 11:26
 */

public class RandomUtil {

    /**
     * 扑克牌 <p>
     */
    private static class Card {


        private static final String[] NORMAL_TYPES = {"黑桃", "红心", "梅花", "方块"};

        private static final String[] QUEEN_TYPES = {"大", "小"};

        private static final String[] WORDS = {"J", "Q", "K", "A"};

        private static final int COUNT = 54;

        private static final String[] CARDS = getCards().toArray(new String[COUNT]);

        private Card() {
        }

        /**
         * 54 <p>
         *
         * @return 获取整套扑克牌
         */
        private static List<String> getCards() {
            List<String> cards = new ArrayList<>();
            for (int i = 2; i <= 10; i++) {
                cardAdd(NORMAL_TYPES, i, cards);
            }
            for (String word :
                    WORDS) {
                cardAdd(NORMAL_TYPES, word, cards);
            }
            cardAdd(QUEEN_TYPES, "鬼", cards);
            return cards;
        }

        private static <T> void cardAdd(String[] types, T i, List<String> list) {
            for (String type :
                    types) {
                list.add("\"" + type + i + "\"");
            }
        }

        /**
         * 获取某张扑克牌的数量 <p>
         *
         * @param card 扑克牌类型
         * @return 该种类扑克牌牌数
         */
        private static int getCount(String card) {
            if (CARDS[CARDS.length - 1].equals(card)) {
                return 2;
            } else {
                return 4;
            }
        }
    }

    /**
     * 分牌结果 <p>
     */
    public static class Result {
        // 份数
        private int copies;
        private List<List<String>> cards;
        private List<String> extralCards;

        public Result(int copies, List<List<String>> cards, List<String> extralCards) {
            this.copies = copies;
            this.cards = cards;
            this.extralCards = extralCards;
        }

        public int size() {
            return copies;
        }

        public Result(int copies, List<List<String>> cards) {
            this.copies = copies;
            this.cards = cards;
        }

        public List<String> getCards(int slot) {
            return cards.get(slot);
        }

        public void printDetails() {
            printDetails("");
        }

        public void printDetails(String extral) {
            System.out.println(extral + "result:{");
            int count = 0;
            for (int i = 0; i < copies; i++) {
                count++;
                System.out.println(i + 1 + ": " + getCards(i) + " count: " + getCards(i).size());
            }
            System.out.println("extral cards: " + extralCards + " count: " + extralCards.size());
            System.out.println("}");
        }
    }

    /**
     * 获取一个范围内的随机数 <p>
     *
     * @param min 范围最小值
     * @param max 范围最大值
     * @return 大于等于min，小于等于max的随机整数
     */
    public static int getRandom(int min, int max) {
        return (int) (Math.random() * Math.abs(max - min) + min);
    }

    /**
     * 获取一个范围内的随机数 <p>
     *
     * @param max 范围最大值
     * @return 大于等于0，小于等于max的随机整数
     */
    public static int getRandom(int max) {
        return getRandom(0, max);
    }

    /**
     * 均匀随机排列数组 <p>
     *
     * @param list 数组
     * @param <T>  数组元素类型
     */
    public static <T> void randomizeInPlace(List<T> list) {
        randomizeInPlace(list, 0, list.size() - 1);
    }

    /**
     * 均匀随机排列数组 <p>
     *
     * @param list 数组
     * @param low 起始
     * @param high 结尾
     * @param <T>  数组元素类型
     */
    public static <T> void randomizeInPlace(List<T> list,
                                            int low,
                                            int high) {
        for (int i = low, random; i <= high; i++) {
            T temp = list.get(i);
            random = getRandom(list.size() - i - 1);
            list.set(i, list.get(i + random));
            list.set(i + random, temp);
        }
    }

    /**
     * 无剩余分牌 <p>
     *
     * @param copies 份数
     * @return Result - 分牌结果
     */
    public static Result getRandomSubCards(int copies) {
        return getRandomSubCards(copies, 0);
    }

    /**
     * 有剩余分牌 <p>
     *
     * @param copies 份数
     * @return Result - 分牌结果
     */
    public static Result getRandomSubCards(int copies, int extralCount) {

        int perCards = (Card.COUNT - extralCount) / copies;

        List<String> cards = Arrays.asList(Card.CARDS);
        randomizeInPlace(cards);

        List<List<String>> cardsResult = new ArrayList<>();
        List<String> extralCards = new ArrayList<>();

        int low, high = 0;
        /*
            分牌
         */
        for (int i = 0; i < copies; i++) {
            low = perCards * i;
            high = low + perCards;
            cardsResult.add(cards.subList(low, high));
        }
        /*
            额外牌
         */
        for (int i = high; i < Card.COUNT; i++) {
            extralCards.add(cards.get(i));
        }

        return new Result(copies, cardsResult, extralCards);
    }

    public static <T extends Weight> T getRandomByWeight(List<T> list,
                                                               int low,
                                                               int high) {
        double sum = getSumWeight(list, low, high);
        int random;
        while ((random = getRandom((int) sum)) == 0) {

        }
        int i;
        for (i = low; i <= high; i++) {
            if ((random -= list.get(i).weight()) <= 0) {
                break;
            }
        }
        return list.get(i);
    }

    /**
     * 概率
     * @param p 概率
     * @return 是否达成概率
     */
    public static boolean p(double p) {
        assert p >= 0 && p <= 1;
        return Math.random() <= p;
    }

    public static <T extends Weight> T getRandomByWeight(List<T> list) {
        return getRandomByWeight(list, 0, list.size() -1);
    }

    private static <T extends Weight> double getSumWeight(List<T> list,
                                                               int low,
                                                               int high) {
        double sum = 0;
        for (int i = low; i <= high; i++) {
            sum += list.get(i).weight();
        }
        return sum;
    }

}
